/**
 * 
 */
package com.gragra.distances;
import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntIterator;
import com.gragra.sampling.vector.VectorStructure;
/**
 * This class is used to implement a distance measure based on probability and a maximum likelihood estimate.
 * Each unit estimated and used in the distance independently.
 * @author Christoph Teichmann
 * 04.05.2013
 */
public class InverseLogTimes implements Mean
{
	/**
	 * @param alpha
	 */
	public InverseLogTimes(double alpha)
	{this.alpha = alpha;}
	/**
	 * the smoothing factor for unseen units
	 */
	private final double alpha;
	/**
	 * this map contains the norms that will be used for the different units once the set method is
	 * called
	 */
	private final Int2DoubleOpenHashMap norms = new Int2DoubleOpenHashMap();
	/**
	 * this is used to keep track of whether a VectorStructure has been added already
	 */
	private boolean empty = true;
	/**
	 * this is used to sum up the different VectorStructure values added so far, once the set method has
	 * been called it will hold the probability estimates
	 */
	private final Int2ObjectMap<Int2DoubleOpenHashMap> sums = new Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>();
	/* (non-Javadoc)
	 * @see com.gragra.distances.Mean#add(com.gragra.sampling.vector.VectorStructure)
	 */
	@Override
	public void add(VectorStructure entry)
	{
		this.empty = false;
		int[][] codes = entry.getCodes();
		double[] values = entry.getValues();
		for(int i=0;i<codes.length;++i)
		{
			int[] code = codes[i];
			double val = values[i];
			add(sums,code[0],code[1],val);
			this.norms.addTo(code[0], val);
		}
	}
	/**
	 * this method is used to add the given value into the map that is being used i is the unit, j is the element
	 * @param sums2
	 * @param i
	 * @param j
	 * @param d
	 */
	private void add(Int2ObjectMap<Int2DoubleOpenHashMap> main, int i, int j,
			double val)
	{
		Int2DoubleOpenHashMap map = main.get(i);
		if(map == null)
		{
			map = new Int2DoubleOpenHashMap();
			main.put(i, map);
		}
		map.addTo(j, val);
	}
	/* (non-Javadoc)
	 * @see com.gragra.distances.Mean#getDistance(com.gragra.sampling.vector.VectorStructure)
	 */
	@Override
	public double getDistance(VectorStructure vs)
	{
		double[] vals = vs.getValues();
		int[][] codes = vs.getCodes();
		double ret = 0.0;
		for(int i=0;i<vals.length;++i)
		{
			int[] code = codes[i];
			double log = this.get(this.sums,code[0],code[1]);
			if(log == 0.0)
			{log = this.norms.get(code[0]);}
			ret += log*vals[i];
		}
		return -ret;
	}
	/**
	 * returns the value currently in the map under unit i and element j
	 * @param sums2
	 * @param i
	 * @param j
	 * @return
	 */
	private double get(Int2ObjectMap<Int2DoubleOpenHashMap> main, int i, int j)
	{
		Int2DoubleOpenHashMap map = main.get(i);
		if(map == null)
		{return 0.0;}
		return map.get(j);
	}
	/* (non-Javadoc)
	 * @see com.gragra.distances.Mean#empty()
	 */
	@Override
	public boolean empty()
	{return this.empty;}
	/* (non-Javadoc)
	 * @see com.gragra.distances.Mean#set()
	 */
	@Override
	public void set()
	{
		for(Entry<Int2DoubleOpenHashMap> ent : this.sums.int2ObjectEntrySet())
		{
			Int2DoubleOpenHashMap map = ent.getValue();
			double norm = this.norms.get(ent.getKey());
			IntIterator iit = map.keySet().iterator();
			while(iit.hasNext())
			{
				int number = iit.nextInt();
				double val = map.get(number);
				map.put(number, Math.log(val)-Math.log(norm+this.alpha));
			}
		}
		IntIterator iit = this.norms.keySet().iterator();
		while(iit.hasNext())
		{
			int unit = iit.nextInt();
			double val = this.norms.get(unit);
			this.norms.put(unit, Math.log(alpha)-Math.log(val+this.alpha));
		}
	}
}